/****************************************************************************
 * Copyright (c) 2010 Markus Alexander Kuppe.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *     Markus Alexander Kuppe (ecf-dev_eclipse.org <at> lemmster <dot> de) - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package org.eclipse.ecf.internal.discovery;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.core.identity.*;
import org.eclipse.ecf.core.util.StringUtils;
import org.eclipse.ecf.discovery.*;
import org.eclipse.ecf.discovery.identity.*;
import org.osgi.framework.*;

public class DiscoveryServiceListener implements ServiceListener {

	private final AbstractDiscoveryContainerAdapter discoveryContainer;
	private final Class listenerClass;
	private final BundleContext context;
	private final IServiceIDFactory idFactory;
	private final Namespace discoveryNamespace;

	public DiscoveryServiceListener(
			AbstractDiscoveryContainerAdapter anAbstractDiscoveryContainerAdapter,
			Class clazz) {
		discoveryContainer = anAbstractDiscoveryContainerAdapter;
		listenerClass = clazz;
		discoveryNamespace = IDFactory.getDefault().getNamespaceByName(
				DiscoveryNamespace.NAME);
		idFactory = ServiceIDFactory.getDefault();
		context = DiscoveryPlugin.getDefault().getBundleContext();
		try {
			// get existing listener
			final ServiceReference[] references = context.getServiceReferences(
					listenerClass.getName(), null);
			addServiceListener(references);

			// listen for more listeners
			context.addServiceListener(this, getFilter());
		} catch (InvalidSyntaxException e) {
			DiscoveryPlugin.getDefault().log(
					new Status(IStatus.ERROR, DiscoveryPlugin.PLUGIN_ID,
							IStatus.ERROR, "Cannot create filter", e)); //$NON-NLS-1$
		}
	}

	public void dispose() {
		if (!DiscoveryPlugin.isStopped()) {
			context.removeServiceListener(this);
		}
	}

	private void addServiceListener(ServiceReference[] references) {
		if (references == null) {
			return;
		}
		for (int i = 0; i < references.length; i++) {
			final ServiceReference serviceReference = references[i];
			if (listenerClass.getName()
					.equals(IServiceListener.class.getName())) {
				if (isAllWildcards(serviceReference)) {
					final IServiceListener aListener = (IServiceListener) context
							.getService(serviceReference);
					discoveryContainer.addServiceListener(aListener);
				} else {
					final IServiceTypeID aType = getIServiceTypeID(serviceReference);
					if (aType == null) {
						continue;
					}
					final IServiceListener aListener = (IServiceListener) context
							.getService(serviceReference);
					discoveryContainer.addServiceListener(aType, aListener);
				}
			} else {
				final IServiceTypeListener aListener = (IServiceTypeListener) context
						.getService(serviceReference);
				discoveryContainer.addServiceTypeListener(aListener);
			}
		}
	}

	private void addServiceListener(ServiceReference reference) {
		addServiceListener(new ServiceReference[] { reference });
	}

	private void removeServiceListener(ServiceReference[] references) {
		if (references == null) {
			return;
		}
		for (int i = 0; i < references.length; i++) {
			final ServiceReference serviceReference = references[i];
			if (listenerClass.getName()
					.equals(IServiceListener.class.getName())) {
				if (isAllWildcards(serviceReference)) {
					final IServiceListener aListener = (IServiceListener) context
							.getService(serviceReference);
					discoveryContainer.removeServiceListener(aListener);
				} else {
					final IServiceTypeID aType = getIServiceTypeID(serviceReference);
					if (aType == null) {
						continue;
					}
					final IServiceListener aListener = (IServiceListener) context
							.getService(serviceReference);
					discoveryContainer.removeServiceListener(aType, aListener);
				}
			} else {
				final IServiceTypeListener aListener = (IServiceTypeListener) context
						.getService(serviceReference);
				discoveryContainer.removeServiceTypeListener(aListener);
			}
		}
	}

	private void removeServiceListener(ServiceReference reference) {
		removeServiceListener(new ServiceReference[] { reference });
	}

	private boolean isAllWildcards(ServiceReference serviceReference) {
		return serviceReference
				.getProperty("org.eclipse.ecf.discovery.namingauthority") == null
				&& serviceReference
						.getProperty("org.eclipse.ecf.discovery.services") == null
				&& serviceReference
						.getProperty("org.eclipse.ecf.discovery.scopes") == null
				&& serviceReference
						.getProperty("org.eclipse.ecf.discovery.protocols") == null;
	}

	private IServiceTypeID getIServiceTypeID(ServiceReference serviceReference) {
		String namingAuthority = (String) serviceReference
				.getProperty("org.eclipse.ecf.discovery.namingauthority"); //$NON-NLS-1$
		if (namingAuthority == null) {
			namingAuthority = "*"; //$NON-NLS-1$
		}
		try {
			final IServiceTypeID createServiceTypeID = idFactory
					.createServiceTypeID(
							discoveryNamespace,
							convert(serviceReference,
									"org.eclipse.ecf.discovery.services"), //$NON-NLS-1$
							convert(serviceReference,
									"org.eclipse.ecf.discovery.scopes"), //$NON-NLS-1$
							convert(serviceReference,
									"org.eclipse.ecf.discovery.protocols"), //$NON-NLS-1$
							namingAuthority);
			return createServiceTypeID;
		} catch (final IDCreateException e) {
			return null;
		}
	}

	private String[] convert(ServiceReference serviceReference, String key) {
		final Object value = serviceReference.getProperty(key);
		// default to wildcard for non-set values
		if (value == null) {
			return new String[] { "*" }; //$NON-NLS-1$
		} else if (value instanceof String[]) {
			return (String[]) value;
		}
		return StringUtils.split((String) value, "._"); //$NON-NLS-1$
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.
	 * ServiceEvent)
	 */
	public void serviceChanged(ServiceEvent event) {
		// ignore events that are targeted at different discovery containers
		final Object containerName = event.getServiceReference().getProperty(
				IDiscoveryLocator.CONTAINER_NAME);
		if (!discoveryContainer.getContainerName().equals(containerName)) {
			return;
		}
		switch (event.getType()) {
		case ServiceEvent.REGISTERED:
			addServiceListener(event.getServiceReference());
			break;
		case ServiceEvent.UNREGISTERING:
			removeServiceListener(event.getServiceReference());
			break;
		default:
			break;
		}
	}

	private String getFilter() {
		return "(" + Constants.OBJECTCLASS + "=" + listenerClass.getName() //$NON-NLS-1$ //$NON-NLS-2$
				+ ")"; //$NON-NLS-1$
	}
}
